import numpy as np
import paddle.fluid as fluid
import time,struct
import matplotlib.pyplot as plt
import matplotlib as mpl

type2code_dict = {'uint8':0x08,'int8':0x09,'int16':0x0B,'int32':0x0C,'float32':0x0D,'float64':0x0E}
code2type_dict = {0x08: 'B', 0x09: 'b', 0x0B: 'h', 0x0c: 'i', 0x0D: 'f', 0x0E: 'd'}
def createLabelList(matrix_list,dtype='>f4'):
    label = []
    #得到4维向量对应的输出值
    shape = matrix_list.shape
    #w = np.random.randint(5, size=(shape[1]+1))
    w = np.array([1,2,3,4,5])
    print(w)
    for i in range(matrix_list.shape[0]):
        # 假设方程式为 y=1a+2b+3c+4d+5
        y = np.dot(w[:-1],matrix_list[i])+w[-1]
        label.append(y)
    noise = np.random.random(len(label))*10#长度为len的一维数组
    noise = np.random.standard_normal(len(label))*10
    # label += noise#数组与列表的数值相加，列表会自动转为数组
    label = np.array(label,dtype=dtype)
    return label

def createMatrixList(len,rows,cols=0,low=10,high=100,dtype='>f4'):
    # 生成数据[0-10)的随机数列
    np.random.seed(int(time.time()))
    if cols == 0:
        matrix_list = np.random.uniform(low=low,high=high,size=(len, rows))
    else:
        matrix_list = np.random.uniform(low=low,high=high,size=(len, rows,cols))

    #matrix_list = np.ascontiguousarray(matrix_list, dtype='>f4')
    matrix_list = matrix_list.astype(dtype)
    return matrix_list

def writeMatrixList(filename,matrix_list):
#补代码---
     with open(filename, 'wb') as f:
        shapes=matrix_list.shape
        file_head_fmt = '>HBB'  # 格式定义，>表示高位在前，I表示4字节整数
        file_head = struct.pack(file_head_fmt,0,type2code_dict[matrix_list.dtype.name],len(shapes))
        f.write( file_head)

        file_head_fmt = '>I'
        for i in  shapes:
            file_head = struct.pack(file_head_fmt, i)
            f.write(file_head)

        f.write( matrix_list)
def readMatrixFromFile(filename,dtype='float32'):
#补充代码-------
    with open(filename,'rb') as f:
        data_buf = f.read()
        off_set = 0
        file_head_fmt = '>HBB'#格式定义，>表示高位在前，I表示4字节整数
        _,dcode,dimslen = struct.unpack_from(file_head_fmt,
					data_buf,off_set)
        off_set += struct.calcsize(file_head_fmt)
        file_head_fmt = '>{}I'.format(dimslen)
        shapes = struct.unpack_from(file_head_fmt,data_buf,off_set)
        off_set += struct.calcsize(file_head_fmt)

        data_fmt = '>'+ str(np.prod(shapes)) + code2type_dict[dcode]
        matrix_list = struct.unpack_from(data_fmt,data_buf,off_set)
        matrix_list = np.reshape(matrix_list, shapes)
    return  matrix_list

matrix_list = createMatrixList(len=20,rows=4)
print(matrix_list)
writeMatrixList('data/linereg.data.idx2.float32',matrix_list)
label_list = createLabelList(matrix_list)
print(label_list)
writeMatrixList('data/linereg.label.idx1.float32', label_list)

print('read')

matrix_list = readMatrixFromFile('data/linereg.data.idx2.float32')
print(matrix_list)

matrix_list = readMatrixFromFile('data/linereg.label.idx1.float32')
print(matrix_list)

print('over')

##  **step1:准备数据**
# 制作生成器，读入所有样本，所有样本顺序先打乱，每次提供batch个样本，
def trainBatchReader(image_filename,label_filename, batch_size=20, drop_last=False):
    image_list = readMatrixFromFile(image_filename)
    label_list = readMatrixFromFile(label_filename)
    #开始补充代码------
    if image_list.shape[0] != label_list.shape[0]:
        raise ("data error !")
    buf = []
    for i in range(image_list.shape[0]):
        buf.append((image_list[i,:], label_list[i]))

    def batch_reader():
        # 样本顺序打乱,即按第一维方向打乱
        np.random.shuffle(buf)
        # 每次迭代提供batch_size个样本
        b =[]
        for instance in buf:
            b.append(instance)
            if len(b) == batch_size:
                yield b
                b = []
        if drop_last is False and len(b) != 0:
            yield b
    # Batch size check
    batch_size = int(batch_size)
    if batch_size <= 0:
        raise ValueError("error")
    return  batch_reader

## ** step2: 定义网络结构**
def deep_linereg_net(input):
    y_predict = fluid.layers.fc(input=input,size=1,name='linereg',act=None)
    return y_predict

def config_network():
    # （1）创建fluid.program
    main_program = fluid.Program()
    startup_program = fluid.Program()
    with fluid.program_guard(main_program=main_program, startup_program=startup_program):
        #补充代码--------------------------
        # 定义输入
        x = fluid.layers.data(name='x', shape=[4], dtype='float32')
        y = fluid.layers.data(name='y', shape=[1], dtype='float32')
        y_predict = deep_linereg_net( x )
        #定义损失函数
        cost = fluid.layers.square_error_cost(input= y_predict ,label= y )
        avg_cost = fluid.layers.mean(cost)
        #定义优化方法
        sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.005)
        sgd_optimizer.minimize( avg_cost )
    return [x,y,startup_program,main_program,y_predict,avg_cost]

# # **Step3.模型训练与保存**
def trainer(train_x_filename,train_y_filename,save_model_dirname,epoch=500,batch_size=5,use_gpu=True):
    x, y, startup_program, main_program, y_predict, avg_cost= config_network()
    train_reader = trainBatchReader(train_x_filename, train_y_filename, batch_size=batch_size, drop_last=False)
    # （1）创建训练的Executor
    place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace()
    exe = fluid.Executor(place)
    # 仅运行一次startup program, 不需要优化/编译这个startup program
    startup_program.random_seed = int(time.time())
    exe.run(startup_program)
    # 定义数据提供器
    feeder = fluid.DataFeeder(place=place, feed_list=[x,y])
    # 进行训练
    for pass_id in range(epoch):
        for batch_id, data in enumerate(train_reader()):  # 遍历train_reader
            train_avg_cost= exe.run(program=main_program,  # 运行主程序
                                        feed=feeder.feed(data),  # 给模型喂入数据
                                        fetch_list=[avg_cost])  # fetch 预测值、误差
            if batch_id % 50 == 0:
                print('Pass:{:d},batch:{:.0f},cost={:f}'.format(pass_id,batch_id,train_avg_cost[0][0]))

    #存储训练结果
    fluid.io.save_inference_model(dirname=save_model_dirname,
                                  feeded_var_names= ['x'],
                                  target_vars=[y_predict],
                                  executor=exe,
                                  main_program= main_program )
    print('train over!')


#绘制真实值和预测值对比图
def draw_infer_result(groud_truths,infer_results):
    mpl.use('tkagg')
    title='linear regression'
    plt.title(title, fontsize=24)
    x = np.arange(1,max(groud_truths))
    y = x
    plt.plot(x, y)
    plt.xlabel('ground truth', fontsize=14)
    plt.ylabel('infer result', fontsize=14)
    plt.scatter(groud_truths, infer_results,color='green',label='training cost')
    plt.grid()
    plt.show()

# # **Step4.模型预测**
def predict_data(train_x_filename,train_y_filename,model_save_dirname):
    inference_scope = fluid.Scope()
    # 加载训练好的模型
    with fluid.scope_guard(inference_scope):
        infer_results = []
        groud_truths = []
        # 开始预测
        cpu = fluid.CPUPlace()
        infer_exe = fluid.Executor(cpu)
        [inference_program,
         feed_target_names,
         fetch_targets] = fluid.io.load_inference_model(model_save_dirname, infer_exe)

        # 生成测试数据
        test = np.array([9,5,2,10])
        test = np.reshape(test,(1,4)).astype('float32')

        # 单样本进行预测
        results = infer_exe.run(program=inference_program,
                                feed={feed_target_names[0]: test},
                                fetch_list=fetch_targets)
        print(results)
        # 给出题目为 【9,5,2,10】 输出y=1*9+2*5+3*2+4*10+5的值
        print ("1*9+2*5+3*2+4*10+5={:f},predict={}".format(1*9+2*5+3*2+4*10+5,results[0]))

        #输出学习到的参数w
        fc_w = fluid.global_scope().var('linereg.w_0').get_tensor()
        arr_fc_w = np.array(fc_w)
        print(arr_fc_w)
        #输出学习到的参数b
        fc_b = fluid.global_scope().find_var('linereg.b_0').get_tensor()
        arr_fc_b = np.array(fc_b)
        print(arr_fc_b)

        #一组样本进行测试
        test_x = readMatrixFromFile(train_x_filename)
        test_y = readMatrixFromFile(train_y_filename)

        results = infer_exe.run(program=inference_program,  # 预测模型
                                feed={feed_target_names[0]: np.array(test_x)},  # 喂入要预测的x值
                                fetch_list=fetch_targets)  # 得到推测结果
        #print("infer results:")
        for idx, val in enumerate(results[0]):
            #print("{:d}: {:.2f}".format (idx, val[0]))
            infer_results.append(val)
        #print("gound truth:")
        for idx, val in enumerate(test_y):
            #print("%d: %.2f" % (idx, val))
            groud_truths.append(val)
        draw_infer_result(groud_truths, infer_results)
        print('predict over!')

# 生成数据
# 假设方程式为 y=1a+2b+3c+4d+5
train_x_filename = 'data/linereg.data.idx2.float32'
train_y_filename = 'data/linereg.label.idx1.float32'
save_model_dirname = "work/linereg.inference.model"
trainer(train_x_filename,train_y_filename,save_model_dirname,epoch=5000,batch_size=10)
predict_data(train_x_filename,train_y_filename,save_model_dirname)
